package chalmers.game.subterranean.model.player;

import chalmers.game.subterranean.event.Bus;
import chalmers.game.subterranean.event.events.player.PlayerLookEvent;
import chalmers.game.subterranean.event.events.player.PlayerMoveEvent;
import chalmers.game.subterranean.model.inventory.Inventory;
import chalmers.game.subterranean.model.items.IEquippableItem;
import chalmers.game.subterranean.model.items.Torch;
import chalmers.game.subterranean.model.physics.MovableBody;
import chalmers.game.subterranean.model.world.IWorld;
import chalmers.game.subterranean.util.BlockType;
import chalmers.game.subterranean.util.Direction;
import chalmers.game.subterranean.util.ExactPosition;
import chalmers.game.subterranean.util.options.WorldValues;
import lombok.Getter;
import lombok.Setter;
import org.apache.log4j.Logger;

/**
 * The player is implemented with the state pattern. Still movements are handled here.
 *
 * @author Alex Evert
 */
public class PlayerContext implements IPlayer {
    private static final Logger logger = Logger.getLogger("subLog");

    // The physical body of the player.
    private @Setter
    MovableBody body;
    // The direction the player is facing.
    private @Setter
    Direction lookDirection;
    // The state of the player.
    private @Setter
    IState currentState;
    // The world the player acts in.
    private @Setter
    IWorld world;
    // Item used when the action method is called.
    private @Setter
    @Getter
    IEquippableItem equippedItem;
    // Torch determining how long the player can stay under ground.
    private @Setter
    @Getter
    Torch torch;
    // The inventory, used to store resources and items.
    private @Setter
    @Getter
    Inventory inventory;

    // The states the player can be in.

    private @Setter
    StateAir airState;
    private @Setter
    IState groundState;
    private @Setter
    IState ladderState;
    private @Setter
    StateWall wallState;

    /**
     * Make the character look in one direction. Used mainly for digging.
     *
     * @param direction the direction the player should face.
     */
    @Override
    public void look(final Direction direction) {
        this.lookDirection = direction;
        Bus.INSTANCE.post(new PlayerLookEvent(direction));
    }

    /**
     * Jump by setting the upwards speed. Airbourne players can't jump.
     */
    @Override
    public void jump() {
        currentState.jump(body);
    }

    /**
     * Run in the given direction, if the player is airbourne there is still some control over the
     * horizontal momentum.
     *
     * @param direction wich direction to run.
     */
    @Override
    public void run(final Direction direction) {
        airState.setLandingSpeedSign(direction.sign());
        checkState(direction);
        currentState.run(body, direction);
    }

    /**
     * Check if we should enter different state. This is something of a hack.
     *
     * @param direction only change state when players try to move up or down
     */
    private void checkState(final Direction direction) {
        if ((direction == Direction.NORTH || direction == Direction.SOUTH)
            && (world.getBlockType(body.getPosition()).action.equals(BlockType.Action.LADDER))) {
            setStateLadder();
        }
    }

    protected BlockType getCurrentBlock() {
        return world.getBlockType(body.getPosition());
    }

    /**
     * Simply stop. Only has effect is the player is standing on ground.
     */
    @Override
    public void stop() {
        airState.setLandingSpeedSign(0.0);
        currentState.stop(body);
    }

    /**
     * Try do use the block the player is currently standing on.
     */
    @Override
    public void use() {
        world.performUse(body.getPosition());
    }

    /**
     * Let the player change wich of her items is currently in use.
     */
    @Override
    public void equipNextItem() {
        final IEquippableItem item = inventory.getNextItem();
        if (item != null) {
            equippedItem = item;
        }
    }

    /**
     * Let the equiped item act on the position the player is looking at.
     */
    @Override
    public void action() {
        equippedItem.action(body.getPosition(), lookDirection);
    }

    /**
     * Change the state of the player. The body and states sometimes need special handling.
     */

    protected void setStateAir() {
        body.setGravityfactor(1);
        currentState = airState;
    }

    protected void setStateGround() {
        body.setGravityfactor(0);
        currentState = groundState;
    }

    protected void setStateLadder() {
        body.setGravityfactor(0);
        currentState = ladderState;
    }

    protected void setStateWall(final Direction direction) {
        wallState.setClingDirection(direction);
        wallState.setTimeOnWall(3);
        body.setGravityfactor(0.1);
        currentState = wallState;
    }

    /**
     * Activate torch when player leaves the top level. Deactivate torch when player enters top level.
     */
    private void torchHandler() {
        if (body.getPosition().depth == 0 && torch.isLit()) {
            torch.extinguishTorch();
        } else if (body.getPosition().depth > 0 && !torch.isLit()) {
            torch.litTorch();
        }
    }

    /**
     * Let the current physics handler move the body.
     */
    @Override
    public void movePlayer() {
        ExactPosition pos;
        currentState.checkSurrounding(body);
        body.moveBody(currentState);
        pos = body.getExactPosition();
        torchHandler();

        // Update UI.
        Bus.INSTANCE
            .post(new PlayerMoveEvent(pos.getDepth(), (int) pos.getX() - WorldValues.BLOCK_SIZE
                / 2, (int) pos.getY()));
    }
}
